package com.benshou.bcss.common.redisson;

import com.benshou.bcss.common.j17common.NoThrowsAutoCloseable;
import com.benshou.bcss.common.j17common.annotation.NotNull;
import com.benshou.bcss.common.j17common.annotation.Nullable;
import com.benshou.bcss.common.lock.EnsureLockWrapper;
import com.benshou.bcss.common.lock.TryLocking;
import lombok.Builder;
import lombok.NonNull;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

/**
 * Redisson 管理器
 *
 * @author <a href="mailto:liuzhenbang@benshou.com">Liu Zhenbang</a>
 */
@Builder
public class RedissonManager {

    /**
     * Redisson 客户端
     */
    @NonNull
    private final RedissonClient redissonClient;

    /**
     * 对象名称的前缀
     */
    @NonNull
    private final String namePrefix;

    /**
     * 获取锁
     *
     * @param key Key
     * @return 锁
     */
    public RLock getLock(@NonNull String key) {
        return redissonClient.getLock(generateLockName(key));
    }

    /**
     * 获取读写锁
     *
     * @param key Key
     * @return 读写锁
     */
    public RReadWriteLock getReadWriteLock(@NonNull String key) {
        return redissonClient.getReadWriteLock(generateLockName(key));
    }

    /**
     * 尝试获取锁占用
     *
     * @param lock 锁实例
     * @param acquireTimeout 尝试获取锁占用的超时时间
     * @param unit 尝试获取锁占用的超时时间的单位
     * @param tryLocking 尝试获取锁占用的方法
     * @return 锁占用资源（资源关闭时解锁）
     * @param <T> 锁的类型
     * @throws InterruptedException 若被外部中断，抛出此异常
     */
    @Nullable
    public <T extends RLock> NoThrowsAutoCloseable acquireTryLock(
            @NonNull T lock,
            long acquireTimeout,
            @NonNull TimeUnit unit,
            @NonNull TryLocking<? super T> tryLocking) throws InterruptedException {
        var lockWrapper = new EnsureLockWrapper<>(lock);
        var isLocked = lockWrapper.tryLock(acquireTimeout, unit, tryLocking);
        return isLocked ?
                () -> {
                    if (lock.isHeldByCurrentThread()) {
                        lock.unlock();
                    }} :
                null;
    }

    /**
     * 尝试获取锁占用
     *
     * @param lock 锁实例
     * @param acquireTimeout 尝试获取锁占用的超时时间
     * @param unit 尝试获取锁占用的超时时间的单位
     * @return 锁占用资源（资源关闭时解锁）
     * @throws InterruptedException 若被外部中断，抛出此异常
     */
    @Nullable
    public NoThrowsAutoCloseable acquireTryLock(
            @NonNull RLock lock,
            long acquireTimeout,
            @NonNull TimeUnit unit) throws InterruptedException {
        return acquireTryLock(
                lock,
                acquireTimeout,
                unit,
                Lock::tryLock);
    }

    /**
     * 尝试获取锁占用
     *
     * @param lock 锁实例
     * @param acquireTimeout 尝试获取锁占用的超时时间
     * @param leaseTimeout 解锁的超时时间，超过此时间，自动解锁
     * @param unit 上述超时时间的单位
     * @return 锁占用资源（资源关闭时解锁）
     * @throws InterruptedException 若被外部中断，抛出此异常
     */
    @Nullable
    public NoThrowsAutoCloseable acquireTryLock(
            @NonNull RLock lock,
            long acquireTimeout,
            long leaseTimeout,
            @NonNull TimeUnit unit) throws InterruptedException {
        return acquireTryLock(
                lock,
                acquireTimeout,
                unit,
                (l, remains, u) -> l.tryLock(remains, u.convert(leaseTimeout, unit), u));
    }

    /**
     * 生成锁对象的名称
     *
     * @param key Key
     * @return 锁对象的名称
     */
    private String generateLockName(@NotNull String key) {
        return this.namePrefix + key;
    }

    /**
     * 构建器
     */
    public static class RedissonManagerBuilder {

        RedissonManagerBuilder() {
            this.namePrefix = "Lock:";
        }
    }
}
